/**
 * 
 */
package com.ws.framework.remoteservice.core.consumer;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ws.framework.remoteservice.core.ex.WRSIException;
import com.ws.framework.remoteservice.core.model.Invocation;
import com.ws.framework.remoteservice.core.model.InvocationImpl;
import com.ws.framework.remoteservice.core.model.Result;
import com.ws.framework.remoteservice.core.model.ServiceKey;
import com.ws.framework.remoteservice.core.protocal.Docker;
import com.ws.framework.remoteservice.core.util.InvocationIdUtils;
import com.ws.framework.remoteservice.core.util.WrsiConstants;

/**
 * @author WSH
 *
 */
public class ServiceAgentImpl implements ServiceAgent, Serializable {

	private final Logger logger = Logger.getLogger(ServiceAgentImpl.class.getName());

	private static final long serialVersionUID = 6332058731264023617L;

	private String constractName;

	private String implCode;
	
	private ServiceKey serviceKey;
	
	private volatile boolean initialized = false;
	
	public ServiceAgentImpl(ServiceKey serviceKey, boolean lazyInit) {
		this.serviceKey = serviceKey;
		this.constractName = serviceKey.getContractName();
		this.implCode = serviceKey.getImplCode();
		if (lazyInit) {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    init();
                }
            };
            thread.setDaemon(true);
            thread.setName("AgentLazyInitThread");
            thread.start();
        } else {
            init();
        }
	}
	
	private void init() {
		if (!initialized) {
			PreconditionExecutor.instance.registerConsumer(serviceKey);
			PreconditionExecutor.instance.createDockers(serviceKey);
            initialized = true;
        }
	}

	@Override
	public String getContract() {
		return constractName;
	}

	@Override
	public String getImplCode() {
		return implCode;
	}

	@Override
	public Object invoke(final Method method, final Object[] args) {
		Docker docker = LoadBalance.Random.select(Collections.<Docker> emptyList(), serviceKey);
		Invocation invocation = makeInvocation(method, args);
		try {
			SyncFuture<Result> future = new SyncFuture<Result>();
			docker.sendUtilSucc(invocation, future);
			Result result = future.get(WrsiConstants.DEFAULT_INVOKE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
			if (result.isSuccess()) {
				return result.getValue();
			} else {
				logger.log(Level.WARNING, "调用远程服务失败... method: " + method.getName() + " args[]: " + args,
						result.getThrowable());
				return new WRSIException(result.getThrowable());
			}
		} catch (Exception e) {
			logger.log(Level.WARNING, "调用远程服务失败... method: " + method.getName() + " args[]: " + args, e);
			throw new WRSIException(e);
		} finally {
			docker.realse(invocation.getId());
		}
	}

	/**
	 * @param constractName
	 *            the constractName to set
	 */
	public void setConstractName(String constractName) {
		this.constractName = constractName;
	}

	/**
	 * @param implCode
	 *            the implCode to set
	 */
	public void setImplCode(String implCode) {
		this.implCode = implCode;
	}

	@Override
	public SyncFuture<Result> invokeWithFuture(Method method, Object[] args) {
		Docker docker = LoadBalance.Random.select(Collections.<Docker> emptyList(), serviceKey);
		Invocation invocation = makeInvocation(method, args);
		SyncFuture<Result> future = new SyncFuture<Result>();
		docker.sendUtilSucc(invocation, future);
		return future;
	}

	@Override
	public void invokeWithCallback(final Method method, final Object[] args, final Callback<Object> callback) {
		Docker docker = LoadBalance.Random.select(Collections.<Docker> emptyList(), serviceKey);
		Invocation invocation = makeInvocation(method, args);
		SyncFuture<Result> future = new SyncFuture<Result>();
		docker.sendUtilSucc(invocation, future);
		Runnable runnable = null;
		try {
			final Result result = future.get(WrsiConstants.DEFAULT_INVOKE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
			if (result.isSuccess()) {
				runnable = new Runnable() {
					@Override
					public void run() {
						callback.handlerResult(result.getValue());
					}
				};
			} else {
				runnable = new Runnable() {
					@Override
					public void run() {
						logger.log(Level.WARNING, "调用远程服务失败... method: " + method.getName() + " args[]: " + args,
								result.getThrowable());
						callback.handlerException(result.getThrowable());
					}
				};
			}
		} catch (final Exception e) {
			runnable = new Runnable() {
				@Override
				public void run() {
					logger.log(Level.WARNING, "调用远程服务失败... method: " + method.getName() + " args[]: " + args, e);
					callback.handlerException(e);
				}
			};
		} finally {
			docker.realse(invocation.getId());
		}
		GenericThreadPool.instance.threadPool.execute(runnable);

	}

	private Invocation makeInvocation(Method method, Object[] args) {
		String id = InvocationIdUtils.nextId();
		Invocation invocation = new InvocationImpl(serviceKey, method.getName(), args,
				method.getParameterTypes(), id);
		return invocation;
	}
	
}
